/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.evaluation;

import cn.ac.nercis.pes.common.utils.DataUtils;
import cn.ac.nercis.pes.common.utils.JsonUtils;
import cn.ac.nercis.pes.common.constant.evaluation.ContentNode;
import cn.ac.nercis.pes.common.constant.evaluation.EvaluationStage;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.common.ResultCode;
import cn.ac.nercis.pes.model.dto.evaluation.content.plan.AnalysisPlanDetailDTO;
import cn.ac.nercis.pes.model.dto.evaluation.content.plan.CompilationPlanDetailDTO;
import cn.ac.nercis.pes.model.dto.evaluation.content.plan.EvaluationPlanDetailDTO;
import cn.ac.nercis.pes.model.dto.evaluation.content.plan.PreparationPlanDetailDTO;
import cn.ac.nercis.pes.model.dto.evaluation.prepare.EvaluationStageDTO;
import cn.ac.nercis.pes.model.dto.evaluation.prepare.EvaluatorDTO;
import cn.ac.nercis.pes.repository.dal.evaluation.EvaluationContentRepository;
import cn.ac.nercis.pes.repository.dal.evaluation.EvaluationProcessRepository;
import cn.ac.nercis.pes.repository.dal.evaluation.EvaluatorRepository;
import cn.ac.nercis.pes.repository.dal.manage.ConstantRepository;
import cn.ac.nercis.pes.repository.mapper.evaluation.EvaluationProcessMapper;
import cn.ac.nercis.pes.repository.model.evaluation.EvaluationContentDO;
import cn.ac.nercis.pes.repository.model.evaluation.EvaluationProcessDO;
import cn.ac.nercis.pes.repository.model.evaluation.EvaluatorDO;
import cn.ac.nercis.pes.repository.model.manage.ConstantDO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 测评过程据服务类
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public class EvaluationProcessService {
    private final EvaluationProcessRepository evaluationProcessRepository;
    private final EvaluatorRepository evaluatorRepository;
    private final EvaluationContentRepository evaluationContentRepository;
    private final ConstantRepository constantRepository;

    @Autowired
    public EvaluationProcessService(EvaluationProcessRepository evaluationProcessRepository,
                                    EvaluatorRepository evaluatorRepository,
                                    EvaluationContentRepository evaluationContentRepository,
                                    ConstantRepository constantRepository) {
        this.evaluationProcessRepository = evaluationProcessRepository;
        this.evaluatorRepository = evaluatorRepository;
        this.evaluationContentRepository = evaluationContentRepository;
        this.constantRepository = constantRepository;
    }

    public EvaluationStageDTO getEvaluationStage(String systemId){
        EvaluationStageDTO evaluationStage = EvaluationStageDTO.builder()
                .systemId(systemId)
                .build();
        List<EvaluationProcessDO> sources= evaluationProcessRepository.findAllBySystemId(systemId);
        sources.stream().filter(ep-> StringUtils.equals(ep.getName(), EvaluationStage.PREPARE.getName()))
                .findFirst()
                .ifPresent(ep->{
                    evaluationStage.setPreparationStartDate(ep.getStartDate());
                    evaluationStage.setPreparationEndDate(ep.getEndDate());
                });
        sources.stream().filter(ep-> StringUtils.equals(ep.getName(), EvaluationStage.PLANNING.getName()))
                .findFirst()
                .ifPresent(ep->{
                    evaluationStage.setCompilationStartDate(ep.getStartDate());
                    evaluationStage.setCompilationEndDate(ep.getEndDate());
                });
        sources.stream().filter(ep-> StringUtils.equals(ep.getName(), EvaluationStage.EVALUATION.getName()))
                .findFirst()
                .ifPresent(ep->{
                    evaluationStage.setEvaluationStartDate(ep.getStartDate());
                    evaluationStage.setEvaluationEndDate(ep.getEndDate());
                });
        sources.stream().filter(ep-> StringUtils.equals(ep.getName(), EvaluationStage.REPORT.getName()))
                .findFirst()
                .ifPresent(ep->{
                    evaluationStage.setAnalysisStartDate(ep.getStartDate());
                    evaluationStage.setAnalysisEndDate(ep.getEndDate());
                });
        evaluationStage.setPreparationPlan(getPreparationPlanDetail(systemId));
        evaluationStage.setCompilationPlan(getCompilationPlanDetail(systemId));
        evaluationStage.setEvaluationPlan(getEvaluationPlanDetail(systemId));
        evaluationStage.setAnalysisPlan(getAnalysisPlanDetail(systemId));
        return evaluationStage;
    }

    public List<EvaluatorDTO> findEvaluators(String systemId){
        return this.evaluatorRepository.findAllBySystemId(systemId)
                .stream()
                .map(EvaluationProcessMapper.INSTANCE::toEvaluatorDTO)
                .collect(Collectors.toList());
    }

    public synchronized Result<String> saveEvaluationState(EvaluationStageDTO evaluationStage){
        Result<String> result = this.savePlanDetail(evaluationStage);
        if(!result.isSuccess()){
            return result;
        }
        result = this.saveEvaluationState(evaluationStage,EvaluationStage.PREPARE);
        if(!result.isSuccess()){
            return result;
        }
        result = this.saveEvaluationState(evaluationStage,EvaluationStage.PLANNING);
        if(!result.isSuccess()){
            return result;
        }
        result = this.saveEvaluationState(evaluationStage,EvaluationStage.EVALUATION);
        if(!result.isSuccess()){
            return result;
        }
        return this.saveEvaluationState(evaluationStage,EvaluationStage.REPORT);
    }

    public Result<EvaluatorDTO> save(EvaluatorDTO evaluator){
        if(StringUtils.isEmpty(evaluator.getName())){
            evaluator.setName("");
        }
        if(StringUtils.isEmpty(evaluator.getRole())){
            evaluator.setRole("");
        }
        try{
            if(StringUtils.isEmpty(evaluator.getId())){
                return create(evaluator);
            }else{
                return modify(evaluator);
            }
        }catch (Exception e){
            log.error("保存测评项目组成员信息出错：",e);
            return Result.failed(ResultCode.FAIL);
        }
    }

    public synchronized Result<String> batchSave(List<EvaluatorDTO> evaluators){
        Result<String> result = Result.success();
        for(EvaluatorDTO item : evaluators){
            Result<EvaluatorDTO> itemResult = save(item);
            if(!itemResult.isSuccess()){
                result = Result.failed(ResultCode.FAIL);
                break;
            }
        }
        return result;
    }

    private Result<String> saveEvaluationState(EvaluationStageDTO evaluationStage,EvaluationStage stage){
        EvaluationProcessDO evaluationProcess = DataUtils.findFirst(evaluationProcessRepository.findBySystemIdAndName(evaluationStage.getSystemId(),stage.getName()))
                .orElse(EvaluationProcessDO.builder()
                        .systemId(evaluationStage.getSystemId())
                        .name(stage.getName())
                        .build());
        if(Objects.isNull(evaluationProcess.getCreateTime())){
            evaluationProcess.setCreateTime(LocalDateTime.now());
        }else{
            evaluationProcess.setModifyTime(LocalDateTime.now());
        }
        switch (stage) {
            case PREPARE -> {
                evaluationProcess.setStartDate(evaluationStage.getPreparationStartDate());
                evaluationProcess.setEndDate(evaluationStage.getPreparationEndDate());
            }
            case PLANNING -> {
                evaluationProcess.setStartDate(evaluationStage.getCompilationStartDate());
                evaluationProcess.setEndDate(evaluationStage.getCompilationEndDate());
            }
            case EVALUATION -> {
                evaluationProcess.setStartDate(evaluationStage.getEvaluationStartDate());
                evaluationProcess.setEndDate(evaluationStage.getEvaluationEndDate());
            }
            default -> {
                evaluationProcess.setStartDate(evaluationStage.getAnalysisStartDate());
                evaluationProcess.setEndDate(evaluationStage.getAnalysisEndDate());
            }
        }
        return Optional.of(evaluationProcess)
                .map(evaluationProcessRepository::save)
                .map(EvaluationProcessDO::getId)
                .map(Result::success)
                .orElse(Result.failed());
    }

    public Result<String> deleteEvaluator(String id){
        try{
            return evaluatorRepository.findById(id)
                    .map(evaluatorDO -> {
                        evaluatorRepository.delete(evaluatorDO);
                        return evaluatorDO;
                    })
                    .map(EvaluatorDO::getId)
                    .map(Result::success)
                    .orElse(Result.failed());
        }catch (Exception ex){
            log.error("删除测评项目组成员信息出错：",ex);
            return Result.failed();
        }
    }

    public Result<String> batchDeleteEvaluator(Set<String> ids){
        Result<String> result = Result.success();
        for(String id : ids){
            Result<String> itemResult = deleteEvaluator(id);
            if(!itemResult.isSuccess()){
                result = Result.failed(ResultCode.FAIL);
                break;
            }
        }
        return result;
    }

    public PreparationPlanDetailDTO getPreparationPlanDetail(String systemId){
        return DataUtils.findFirst(this.evaluationContentRepository.findBySystemIdAndNode(systemId, ContentNode.PREPARATION_PLAN_DETAIL))
                .map(data-> JsonUtils.fromJson(data.getData(),PreparationPlanDetailDTO.class))
                .orElse(PreparationPlanDetailDTO.builder()
                        .projectStart(defaultContent(EvaluationStage.PREPARE_PROJECT_START))
                        .projectStartCycle("1")
                        .collectAnalyze(defaultContent(EvaluationStage.PREPARE_COLLECT_ANALYZE))
                        .collectAnalyzeCycle("1")
                        .toolPrepare(defaultContent(EvaluationStage.PREPARE_TOOLS_PREPARE))
                        .toolPrepareCycle("1")
                        .build());
    }

    public CompilationPlanDetailDTO getCompilationPlanDetail(String systemId){
        return DataUtils.findFirst(this.evaluationContentRepository.findBySystemIdAndNode(systemId,ContentNode.COMPILATION_PLAN_DETAIL))
                .map(data-> JsonUtils.fromJson(data.getData(),CompilationPlanDetailDTO.class))
                .map(data->{
                    if(StringUtils.isEmpty(data.getCheckPointDefine()) && StringUtils.isEmpty(data.getCheckPointDefineCycle())){
                        data.setCheckPointDefine(defaultContent(EvaluationStage.PLANNING_CHECK_POINT_DEFINE));
                        data.setCheckPointDefineCycle("1");
                    }
                    return data;
                })
                .orElse(CompilationPlanDetailDTO.builder()
                        .assetDefine(defaultContent(EvaluationStage.PLANNING_ASSET_DEFINE))
                        .assetDefineCycle("1")
                        .checkPointDefine(defaultContent(EvaluationStage.PLANNING_CHECK_POINT_DEFINE))
                        .checkPointDefineCycle("1")
                        .targetDefine(defaultContent(EvaluationStage.PLANNING_TARGET_DEFINE))
                        .targetDefineCycle("1")
                        .contentDefine(defaultContent(EvaluationStage.PLANNING_CONTENT_DEFINE))
                        .contentDefineCycle("1")
                        .schemeMake(defaultContent(EvaluationStage.PLANNING_SCHEME_MAKE))
                        .schemeMakeCycle("1")
                        .build());
    }

    public EvaluationPlanDetailDTO getEvaluationPlanDetail(String systemId){
        return DataUtils.findFirst(this.evaluationContentRepository.findBySystemIdAndNode(systemId,ContentNode.EVALUATION_PLAN_DETAIL))
                .map(data-> JsonUtils.fromJson(data.getData(),EvaluationPlanDetailDTO.class))
                .orElse(EvaluationPlanDetailDTO.builder()
                        .implementPrepare(defaultContent(EvaluationStage.EVALUATION_IMPLEMENT_PREPARE))
                        .implementPrepareCycle("1")
                        .sceneRecord(defaultContent(EvaluationStage.EVALUATION_SCENE_RECORD))
                        .sceneRecordCycle("1")
                        .resultDefine(defaultContent(EvaluationStage.EVALUATION_RESULT_DEFINE))
                        .resultDefineCycle("1")
                        .build());
    }

    public AnalysisPlanDetailDTO getAnalysisPlanDetail(String systemId){
        return DataUtils.findFirst(this.evaluationContentRepository.findBySystemIdAndNode(systemId,ContentNode.ANALYSIS_PLAN_DETAIL))
                .map(data-> JsonUtils.fromJson(data.getData(),AnalysisPlanDetailDTO.class))
                .map(data->{
                    if(StringUtils.isEmpty(data.getUnitEval())
                            && StringUtils.isEmpty(data.getOverallEval())
                            && StringUtils.isEmpty(data.getQuantifyEval())){
                        data.setUnitEval(defaultContent(EvaluationStage.REPORT_UNIT_EVAL));
                        data.setUnitEvalCycle("1");
                        data.setOverallEval(defaultContent(EvaluationStage.REPORT_OVERALL_EVAL));
                        data.setOverallEvalCycle("1");
                        data.setQuantifyEval(defaultContent(EvaluationStage.REPORT_QUANTIFY_EVAL));
                        data.setQuantifyEvalCycle("1");
                        data.setRiskAnalyze(defaultContent(EvaluationStage.REPORT_RISK_ANALYZE));
                        data.setRiskAnalyzeCycle("1");
                        data.setEvalConclusion(defaultContent(EvaluationStage.REPORT_EVAL_CONCLUSION));
                        data.setEvalConclusionCycle("1");
                        data.setSecretReport(defaultContent(EvaluationStage.REPORT_SECRET_REPORT));
                        data.setSecretReportCycle("1");

                    }
                    return data;
                })
                .orElse(AnalysisPlanDetailDTO.builder()
                        .unitEval(defaultContent(EvaluationStage.REPORT_UNIT_EVAL))
                        .unitEvalCycle("1")
                        .overallEval(defaultContent(EvaluationStage.REPORT_OVERALL_EVAL))
                        .overallEvalCycle("1")
                        .quantifyEval(defaultContent(EvaluationStage.REPORT_QUANTIFY_EVAL))
                        .quantifyEvalCycle("1")
                        .riskAnalyze(defaultContent(EvaluationStage.REPORT_RISK_ANALYZE))
                        .riskAnalyzeCycle("1")
                        .evalConclusion(defaultContent(EvaluationStage.REPORT_EVAL_CONCLUSION))
                        .evalConclusionCycle("1")
                        .secretReport(defaultContent(EvaluationStage.REPORT_SECRET_REPORT))
                        .secretReportCycle("1")
                        .build());
    }

    private Result<EvaluatorDTO> create(EvaluatorDTO evaluator){
        return Optional.of(EvaluationProcessMapper.INSTANCE.toEvaluatorDO(evaluator))
                .map(e->{
                    e.setCreateTime(LocalDateTime.now());
                    return e;
                })
                .map(evaluatorRepository::save)
                .map(EvaluationProcessMapper.INSTANCE::toEvaluatorDTO)
                .map(Result::success)
                .orElse(Result.failed());
    }

    private Result<EvaluatorDTO> modify(EvaluatorDTO evaluator){
        EvaluatorDO source = this.evaluatorRepository.findById(evaluator.getId()).orElse(null);
        if(Objects.isNull(source)){
            return Result.failed(ResultCode.NOT_FOUND);
        }
        return Optional.of(source)
                .map(s->{
                    s.setName(evaluator.getName());
                    s.setRole(evaluator.getRole());
                    s.setDesc(evaluator.getDesc());
                    s.setPassTest(evaluator.getPassTest());
                    s.setModifyTime(LocalDateTime.now());
                    return s;
                })
                .map(evaluatorRepository::save)
                .map(EvaluationProcessMapper.INSTANCE::toEvaluatorDTO)
                .map(Result::success)
                .orElse(Result.failed(ResultCode.FAIL));
    }

    private Result<String> savePlanDetail(EvaluationStageDTO evaluationStage){
        Result<String> result = this.savePlanDetail(evaluationStage.getSystemId(),ContentNode.PREPARATION_PLAN_DETAIL,
                JsonUtils.toJson(evaluationStage.getPreparationPlan()));
        if(!result.isSuccess()){
            return result;
        }
        result = this.savePlanDetail(evaluationStage.getSystemId(),ContentNode.COMPILATION_PLAN_DETAIL,
                JsonUtils.toJson(evaluationStage.getCompilationPlan()));
        if(!result.isSuccess()){
            return result;
        }
        result = this.savePlanDetail(evaluationStage.getSystemId(),ContentNode.EVALUATION_PLAN_DETAIL,
                JsonUtils.toJson(evaluationStage.getEvaluationPlan()));
        if(!result.isSuccess()){
            return result;
        }
        return this.savePlanDetail(evaluationStage.getSystemId(),ContentNode.ANALYSIS_PLAN_DETAIL,
                JsonUtils.toJson(evaluationStage.getAnalysisPlan()));
    }

    private Result<String> savePlanDetail(String systemId,ContentNode node,String data){
        EvaluationContentDO evaluationContentDO = DataUtils.findFirst(this.evaluationContentRepository.findBySystemIdAndNode(systemId,node))
                .map(ec->{
                    ec.setNode(node);
                    ec.setData(data);
                    ec.setModifyTime(LocalDateTime.now());
                    return ec;
                })
                .orElse(EvaluationContentDO.builder()
                        .systemId(systemId)
                        .node(node)
                        .data(data)
                        .build());
        if(StringUtils.isEmpty(evaluationContentDO.getId())){
            evaluationContentDO.setCreateTime(LocalDateTime.now());
        }
        return Optional.of(evaluationContentDO)
                .map(this.evaluationContentRepository::save)
                .map(EvaluationContentDO::getId)
                .map(Result::success)
                .orElse(Result.failed());
    }

    private String defaultContent(EvaluationStage plan){
        return DataUtils.findFirst(constantRepository.findByName(plan.getName()))
                .map(ConstantDO::getValue)
                .orElse("");
    }
}